use crate::AnalyzerError;

use rusqlite::named_params;

const REPEAT_TIME: u32 = 10;

type Conn = rusqlite::Connection;

pub struct Cache {
    map: tsp::map::Map,
    gid: u32,
    conn: Conn,
}

impl Cache {
    pub fn new<T>(conn: Conn, map_file: T, gid: u32) -> Result<Cache, AnalyzerError>
    where
        T: AsRef<str>,
    {
        log::info!("Try create table");
        conn.execute(
            "CREATE TABLE IF NOT EXISTS cache (
                id INTEGER PRIMARY KEY NOT NULL,
                pn INTEGER NOT NULL,
                pm REAL NOT NULL,
                pc REAL NOT NULL,
                gid INTEGER NOT NULL,
                times INTEGER NOT NULL,
                result REAL NOT NULL
            )",
            [],
        )?;
        Ok(Cache {
            conn,
            gid,
            map: tsp::read_map(map_file.as_ref())?,
        })
    }
    pub fn get_value(&self, pn: u32, pm: f64, pc: f64, times: u32) -> Result<f64, AnalyzerError> {
        let cache = query(&self.conn, self.gid, pn, pm, pc, times)?;

        match cache {
            Some(value) => Ok(value),
            None => insert(&self.conn, &self.map, self.gid, pn, pm, pc, times),
        }
    }
}

pub fn query(
    conn: &Conn,
    gid: u32,
    pn: u32,
    pm: f64,
    pc: f64,
    times: u32,
) -> Result<Option<f64>, AnalyzerError> {
    log::debug!("({},{},{},{})", pn, pm, pc, times);
    let mut stmt = conn.prepare(
        "SELECT result FROM cache WHERE gid = :gid AND pn = :pn AND pm = :pm AND pc = :pc AND times = :times",
    )?;

    let rows = stmt.query_map(
        named_params! {
             ":gid": gid,
             ":pn": pn,
             ":pm": pm,
             ":pc": pc,
             ":times": times,
        },
        |row| row.get::<_, f64>(0),
    )?;

    let mut result: Vec<f64> = vec![];
    for row in rows {
        result.push(row?);
    }

    match result.len() {
        0 => Ok(None),
        1 => Ok(Some(result[0])),
        _ => Err(AnalyzerError::MoreResults),
    }
}

pub fn insert(
    conn: &Conn,
    map: &tsp::map::Map,
    gid: u32,
    pn: u32,
    pm: f64,
    pc: f64,
    times: u32,
) -> Result<f64, AnalyzerError> {
    log::info!("Not Found in cache, calcuate");
    let mut stmt = conn.prepare(
        "INSERT INTO cache (gid, pn, pm, pc, times, result) VALUES (:gid, :pn, :pm, :pc, :times, :result )",
    )?;

    let mut result = vec![0.; (times + 10) as usize];

    for _i in 0..REPEAT_TIME {
        let mut population = tsp::population::Population::new(map.clone(), pn, pm, pc);
        for i in 1..=times {
            result[i as usize] += population
                .iterator()
                .ok_or(AnalyzerError::TspError)?
                .fitness();
        }
    }

    for i in 1..=times {
        if query(&conn, gid, pn, pm, pc, i)?.is_none() {
            result[i as usize] /= REPEAT_TIME as f64;
            stmt.execute(named_params! {
                ":gid": gid,
                ":pn": pn,
                ":pm": pm,
                ":pc": pc,
                ":times": i,
                ":result": result[i as usize]
            })?;
        }
    }

    Ok(result[times as usize])
}
